/*
 * svm_test.cc
 *
 *  Created on: Oct 12, 2011
 *      Author: tqlong
 */

#include <iostream>
#include <fstream>
#include <sstream>
#include <boost/program_options.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/foreach.hpp>
#include <armadillo>

//#include "../archive.h"

namespace po = boost::program_options;
po::options_description desc("Allowed options");
po::variables_map vm;

void process_options(int argc, char** argv);
void usage(int exitcode = 0)
{
  std::cout << desc << std::endl;
  exit(exitcode);
}

void FATAL_ERROR(std::string reason, std::string error)
{
  std::cerr << error << " : " << reason << std::endl;
  exit(1);
}

/*
 *
-s svm_type : set type of SVM (default 0)
  0 -- C-SVC
  1 -- nu-SVC
  2 -- one-class SVM
  3 -- epsilon-SVR
  4 -- nu-SVR
-t kernel_type : set type of kernel function (default 2)
  0 -- linear: u'*v
  1 -- polynomial: (gamma*u'*v + coef0)^degree
  2 -- radial basis function: exp(-gamma*|u-v|^2)
  3 -- sigmoid: tanh(gamma*u'*v + coef0)
  4 -- precomputed kernel (kernel values in training_set_file)
-d degree : set degree in kernel function (default 3)
-g gamma : set gamma in kernel function (default 1/num_features)
-r coef0 : set coef0 in kernel function (default 0)
-c cost : set the parameter C of C-SVC, epsilon-SVR, and nu-SVR (default 1)
-n nu : set the parameter nu of nu-SVC, one-class SVM, and nu-SVR (default 0.5)
-p epsilon : set the epsilon in loss function of epsilon-SVR (default 0.1)
-m cachesize : set cache memory size in MB (default 100)
-e epsilon : set tolerance of termination criterion (default 0.001)
-h shrinking : whether to use the shrinking heuristics, 0 or 1 (default 1)
-b probability_estimates : whether to train a SVC or SVR model for probability estimates, 0 or 1 (default 0)
-wi weight : set the parameter C of class i to weight*C, for C-SVC (default 1)
-v n: n-fold cross validation mode
-q : quiet mode (no outputs)
 *
 */
struct SVM_Options
{
  int svm_type;
  int kernel_type;
  int degree;
  double gamma;
  double coef0;
  double cost;
  double nu;
  double loss_epsilon;
  int cachesize;
  double tol_epsilon;
  int shrinking;
  int probability;
  double weight;
  int fold;
  int quiet;

  SVM_Options() :
    svm_type(0), kernel_type(1), degree(3), gamma(0), coef0(-1), cost(0), fold(10), quiet(1)
  {

  }

  void csvc_poly(int degree, double gamma, double coef0, double cost)
  {
    svm_type = 0;
    kernel_type = 1;
    this->degree = degree;
    this->gamma = gamma;
    this->coef0 = coef0;
    this->cost = cost;
  }

  void csvc_rbf(double gamma, double cost)
  {
    svm_type = 0;
    kernel_type = 2;
    this->gamma = gamma;
    this->cost = cost;
  }

  std::string str() const
  {
    std::stringstream ss;
    ss << " -s " << svm_type << " ";
    ss << "-t " << kernel_type << " ";
    ss << "-d " << degree << " ";
    if (gamma > 0)
      ss << "-g " << gamma << " ";
    if (coef0 >= 0)
      ss << "-r " << coef0 << " ";
    if (cost > 0)
      ss << "-c " << cost << " ";
    if (fold > 0)
      ss << "-v " << fold << " ";
    if (quiet)
      ss << "-q ";
    return ss.str();
  }
};

void generate_options(std::vector<SVM_Options>& opts)
{
  double all_gamma[] = {.1, 1.0, 10.0};
  double all_coef0[] = {.1, 1.0, 10.0};
  double all_C[] = {.1, 1.0, 10.0};

  for (int d = 1; d <= 10; d++)
  {
    BOOST_FOREACH(double gamma, all_gamma)
    {
      BOOST_FOREACH(double coef0, all_coef0)
      {
        BOOST_FOREACH(double C, all_C)
        {
          opts.push_back(SVM_Options());
          opts.back().csvc_poly(d, gamma, coef0, C);
        }
      }
    }
  }

  BOOST_FOREACH(double gamma, all_gamma)
  {
    BOOST_FOREACH(double C, all_C)
    {
      opts.push_back(SVM_Options());
      opts.back().csvc_rbf(gamma, C);
    }
  }
}

void do_loocv()
{
  std::string svm_train_path = vm["svm"].as<std::string>();
  std::string data_path = vm["loocv"].as<std::string>();
  std::vector<SVM_Options> options;

  generate_options(options);

  BOOST_FOREACH(const SVM_Options& opt, options)
  {
    std::string command = svm_train_path + opt.str() + data_path;
    std::cout << "command=" << command << ";   "; std::cout.flush();
    std::cerr << command << std::endl;
    int ret_val = system(command.c_str()); ret_val++;
  }
}

void do_gen_functional_data()
{
  std::string filename = vm["gen"].as<std::string>();
  std::ofstream file(filename.c_str());
  int N = 100;
  int d = 10;
  for (int i = 0; i < N; i++) 
  {
    int label = (arma::randu() > 0.5) ? 1 : -1;
    file << (label > 0 ? "+1" : "-1") << " ";
    for (int j = 0; j < d; j++) 
    {
      double x = (double) j / (d-1);
      if (arma::randu() > 0.3) 
      {
	double y = (label > 0) ? (x-0.5)*(x-0.5) : (x-0.5)*(x-0.5)*(x-0.5);
	file << j << ":" << y << " ";
      }
    }
    file << std::endl;
  }
}

int main(int argc, char** argv)
{
  std::cout << "svm_test ..." << std::endl;

  process_options(argc, argv);

  boost::posix_time::ptime time_start(boost::posix_time::microsec_clock::local_time());

  if (vm.count("help"))
    usage();

  if (vm.count("loocv"))
  {
    do_loocv();
  }

  if (vm.count("gen"))
  {
    do_gen_functional_data();
  }

  boost::posix_time::ptime time_end(boost::posix_time::microsec_clock::local_time());
  boost::posix_time::time_duration duration(time_end - time_start);
  std::cerr << "Duration = " << duration << "\n";

  //usage();
}

void process_options(int argc, char** argv)
{
  desc.add_options()
    ("help", "produce this usage")
    ("svm", po::value<std::string>()->default_value("./svm-train"), "path to svm-train")
    ("loocv", po::value<std::string>(), "path to data file to do LOOCV")
    //("append", po::value<std::string>(), "append a file")
    //("to", po::value<std::string>(), "to the archive file")
    //("fin", po::value<std::string>(&fin)->default_value(""), "input file (factor functions)")
    //("out", po::value<std::string>(&out)->default_value(""), "output file")
    ("gen", po::value<std::string>(), "functional data set")
    ;
  po::store(po::parse_command_line(argc, argv, desc), vm);
  po::notify(vm);
}

